import { log } from "console";

/**ts加不加?号的区别，？为可选属性 */
interface Person {
    name: string;
    age?: number; // 可选属性
}
const aa: Person = {
    name: '55555',
}
/**只读属性 */
interface Point {
    readonly x: number;
    readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
/**TypeScript具有ReadonlyArray<T>类型，它与Array<T>相似，只是把所有可变方法去掉了，因此可以确保数组创建后再也不能被修改： */
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
/**上面代码的最后一行，可以看到就算把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写： */
a = ro as number[];
/**readonly vs const
 *最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const，若做为属性则使用readonly。
 */

/**类型断言(尖括号语法和as语法) */
const someValue2: any = "this is a string";
const strLength2: number = (<string>someValue2).length;

const someValue3: any = "this is a string";
const strLength3: number = (someValue3 as string).length;

/**函数类型 */
interface SearchFunc {
    (source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function (source: string, subString: string) {
    const result = source.search(subString);
    return result > -1;
}
//对于函数类型的类型检查来说，函数的参数名不需要与接口里定义的名字相匹配。
const mySearchs: SearchFunc = function (src: string, sub: string): boolean {
    const result = src.search(sub);
    return result > -1;
}
// 函数的参数会逐个进行检查，要求对应位置上的参数类型是兼容的。 
// 如果你不想指定类型，TypeScript的类型系统会推断出参数类型，
// 因为函数直接赋值给了 SearchFunc类型变量。 函数的返回值类型是通过其返回值推断出来的（此例是 false和true）。 
// 如果让这个函数返回数字或字符串，类型检查器会警告我们函数的返回值类型与 SearchFunc接口中的定义不匹配。
const mySearchd: SearchFunc = function (src, sub) {
    const result = src.search(sub);
    return result > -1;
}
/**可索引的类型 */
// 与使用接口描述函数类型差不多，我们也可以描述那些能够“通过索引得到”的类型，比如a[10]或ageMap["daniel"]。
// 可索引类型具有一个 索引签名，它描述了对象索引的类型，还有相应的索引返回值类型。 让我们看一个例子
//myArray 的索引为数字，但由于 StringArray 接口中定义了索引类型为 string，TypeScript 会将数字索引隐式转换为字符串索引，
//因此 myArray[0] 和 myArray["0"] 实际上是等价的，都可以正确地访问到属性。
interface StringArray {
    [index: string]: string;
}

const myArray: StringArray = {
    "0": "Hello",
    "1": "World"
};
const firstItem = myArray["0"]; // 可以通过字符串索引来访问属性

// TypeScript支持两种索引签名：字符串和数字。 可以同时使用两种类型的索引，
// 但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时，
// javaScript会将它转换成string然后再去索引对象。 
// 也就是说用 100（一个number）去索引等同于使用"100"（一个string）去索引，因此两者需要保持一致。

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// 错误：使用数值型的字符串索引，有时会得到完全不同的Animal!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}
interface NumberDictionary {
    [index: string]: number;
    length: number;    // 可以，length是number类型
    name: string       // 错误，`name`的类型与索引类型返回值的类型不匹配
}
/**类类型 */
interface ClockInterface {
    currentTime: Date;
}

//通过使用 implements，是用来表示一个类实现（或者说遵循）一个接口的关键字，
//可以确保一个类满足了特定接口的要求，这样在编译时会进行类型检查，以确保类中包含了接口中定义的所有成员。
class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) {
        this.currentTime = new Date();
    }
}
/**混合类型 */
interface Counter {
    (start: number): string;//函数签名，它规定了函数应该接受的参数类型和返回的值类型。
    interval: number;//属性
    reset(): void;//方法
}

function getCounter(): Counter {
    const counter = <Counter>function (start: number) { };//此处为强制类型转换
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

const c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
/**接口函数类 */
class Control {
    private state: any;//私有属性
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 错误：“Image”类型缺少“state”属性。
class Image implements SelectableControl {
    select() { }
}
/**类继承 */
class Animal {
    constructor(public name: string) { }
    move(distance: number) {
        console.log(`${this.name} moved ${distance}m.`);
    }
}

class Dog extends Animal {
    //默认调用了super（）
    bark() {
        console.log('Woof! Woof!');
    }
}

let dog = new Dog('Buddy');
dog.move(10); // 输出: Buddy moved 10m.
dog.bark(); // 输出: Woof! Woof!

/**类公有和私有属性，只读属性 */
class Animals {
    readonly six: '男';//只读属性
    public age: 48;//公共属性，可以省略public
    private name: string;//私有属性
    constructor(theName: string) { this.name = theName; }
}

new Animals("Cat").name; // 错误: 'name' 是私有的.

/**泛型（可重用性，以自己的数据类型来使用组件） */
function identity<T>(arg: T): T {
    return arg;
}
//使用 第一种是，传入所有的参数，包含类型参数：
const output = identity<string>("myString");  // type of output will be 'string'
// 第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型：
const output2 = identity("myString");  // type of output will be 'string'
//现在假设我们想操作T类型的数组而不直接是T。由于我们操作的是数组，所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组：
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}
//这样也行
function loggingIdentity2<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}
/**泛型类型 */
function identity1<T>(arg: T): T {
    return arg;
}
const myIdentity: <T>(arg: T) => T = identity1;
//或
const myIdentity2: <U>(arg: U) => U = identity1;
//还可以使用带有调用签名的对象字面量来定义泛型函数：
const myIdentity3: { <T>(arg: T): T } = identity1;
//然后可以变成
interface GenericIdentityFn {
    <T>(arg: T): T;
}
const myIdentity4: GenericIdentityFn = identity1;
/**
 * 一个相似的例子，我们可能想把泛型参数当作整个接口的一个参数。
 *  这样我们就能清楚的知道使用的具体是哪个泛型类型（比如： Dictionary<string>而不只是Dictionary）。
 *  这样接口里的其它成员也能知道这个参数的类型了。
 */
interface GenericIdentityFn1<T> {
    (arg: T): T;
}
const myIdentity5: GenericIdentityFn1<number> = identity1;

/**泛型类 */
//泛型类看上去与泛型接口差不多。 泛型类使用（ <>）括起泛型类型，跟在类名后面。
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

const myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
/**泛型约束 */
function loggingIdentity3<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}
interface Lengthwise {
    length: number;
}

function loggingIdentity4<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}
loggingIdentity4(3);  // Error, number doesn't have a .length property
loggingIdentity4({ length: 10, value: 3 });

/**在泛型约束中使用类型参数 */
//以声明一个类型参数，且它被另一个类型参数所约束，keyof 是 TypeScript 中的一个索引类型查询操作符，用于获取指定类型的所有键的联合类型。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}
const x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
/**类型别名 */

type Points = {
    x: number;
    y: number;
};

type Circle = {
    center: Points;
    radius: number;
};

type Employee = {
    id: number;
    name: string;
    role: "admin" | "user";
};
type Callback = (data: string) => void;
/**symbol类型（不可改变，唯一） */
// 创建一个名为 key 的 Symbol 值
const key = Symbol('description');
// 使用 Symbol 作为对象的属性键
const obj = {
    [key]: 'value'
};
// 通过 Symbol 键访问对象的属性
console.log(obj[key]); // 输出 'value'
/**导入导出 */
// 将整个模块导入到一个变量，并通过它来访问模块的导出部分
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();


